/**
 ******************************************************************************
 * @file    ymodem.c
 * @author
 * @version V1.0.0
 * @date
 * @brief   This file provides all the software functions related to the ymodem
 *          protocol.
 ******************************************************************************
 * @attention
 *
 * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
 * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
 * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
 * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
 * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
 * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
 *
 ******************************************************************************
 */

/**
 * @{
 */

/* Includes ------------------------------------------------------------------*/
// #include "flash_if.h"
#include <stdio.h>
#include <string.h>
#include "common.h"
#include "ymodem.h"
#include "debug_uart.h"
#include "flash_efc.h"
#include "md5.h"
#include "download.h"
#include "bootloader.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint8_t FileName[128];
ota_file_header_t ota_file_header;
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/**
 * @brief  Receive byte from sender
 * @param  c: Character
 * @param  timeout: Timeout
 * @retval 0: Byte received
 *        -1: Timeout
 */
static int32_t Receive_Byte(uint8_t *c, uint32_t timeout)
{
  while (timeout-- > 0)
  {
    if (SerialKeyPressed(c) == 1)
    {
      return 0;
    }
  }
  return -1;
}

/**
 * @brief  Send a byte
 * @param  c: Character
 * @retval 0: Byte sent
 */
static uint32_t Send_Byte(uint8_t c)
{
  SerialPutChar(c);
  return 0;
}

/**
 * @brief  Receive a packet from sender
 * @param  data
 * @param  length
 * @param  timeout
 *     0: end of transmission
 *    -1: abort by sender
 *    >0: packet length
 * @retval 0: normally return
 *        -1: timeout or packet error
 *         1: abort by user
 */
static int32_t Receive_Packet(uint8_t *data, int32_t *length, uint32_t timeout)
{
  uint16_t i, packet_size;
  uint8_t c;
  *length = 0;
  if (Receive_Byte(&c, timeout) != 0)
  {
    return -1;
  }
  switch (c)
  {
  case SOH:
    packet_size = PACKET_SIZE;
    // debug_print("SOH packet_size:%d\r\n",packet_size);
    break;
  case STX:
    packet_size = PACKET_1K_SIZE;
    // debug_print("STX packet_size:%d\r\n",packet_size);
    break;
  case EOT:
    return 0;
  case CA:
    if ((Receive_Byte(&c, timeout) == 0) && (c == CA))
    {
      *length = -1;
      return 0;
    }
    else
    {
      return -1;
    }
  case ABORT1:
  case ABORT2:
    return 1;
  default:
    return -1;
  }
  *data = c;
  for (i = 1; i < (packet_size + PACKET_OVERHEAD); i++)
  {
    if (Receive_Byte(data + i, timeout) != 0)
    {
      return -1;
    }
  }
  if (data[PACKET_SEQNO_INDEX] != ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff)) // 序号和补码
  {
    return -1;
  }
  *length = packet_size;
  return 0;
}

/**
 * @brief  Receive a file using the ymodem protocol.
 * @param  buf: Address of the first byte.
 * @retval The size of the file.
 */
uint32_t flashdestination;

int32_t Ymodem_Receive(uint8_t *buf)
{
  uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD], file_size[FILE_SIZE_LENGTH], *file_ptr, *buf_ptr;
  int32_t i, packet_length, session_done, file_done, packets_received, errors, session_begin, size = 0;
  uint32_t ramsource;
  uint8_t ul_rc;
  uint32_t timeout = 0;

  /* Initialize flashdestination variable */
  for (session_done = 0, errors = 0, session_begin = 0;;)
  {
    for (packets_received = 0, file_done = 0, buf_ptr = buf;;)
    {
      switch (Receive_Packet(packet_data, &packet_length, NAK_TIMEOUT))
      {
      case 0:
        errors = 0;
        switch (packet_length)
        {
        /* Abort by sender */
        case -1:
          Send_Byte(ACK);
          return 0;
        /* End of transmission */
        case 0:
          Send_Byte(ACK);
          file_done = 1;
          break;
        /* Normal packet */
        default:
          debug_print("packet length:%d\r\n", packet_length);
          if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))
          {
            Send_Byte(NAK);
          }
          else
          {
            timeout = 0;
            if (packets_received == 0) // 文件名
            {
              flashdestination = 0;

              // 帧头	包号	包号反码	   文件名称    	   文件大小	      填充区	   校验高位	 校验低位
              // SOH	 0x00	 0xFF    	File name+0x00	File size+0x00	NULL(0x00)	CRC-H	    CRC-L
              /* Filename packet */
              if (packet_data[PACKET_HEADER] != 0)
              {
                /* Filename packet has valid data */
                for (i = 0, file_ptr = packet_data + PACKET_HEADER; (*file_ptr != 0) && (i < FILE_NAME_LENGTH);)
                {
                  FileName[i++] = *file_ptr++;
                }
                FileName[i++] = '\0';
                debug_print("file name:%s\r\n", FileName);

                for (i = 0, file_ptr++; (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH);)
                {
                  file_size[i++] = *file_ptr++;
                }
                file_size[i++] = '\0';
                Str2Int(file_size, &size);

                debug_print("file size:%d\r\n", size);

                /* Test the size of the image to be sent */
                /* Image size is greater than Flash size */
                if (size > (USER_FLASH_SIZE + 1))
                {
                  /* End session */
                  Send_Byte(CA);
                  Send_Byte(CA);
                  return -1;
                }
                Send_Byte(ACK);
                Send_Byte(CRC16);
              }
              /* Filename packet is empty, end session */
              else
              {
                Send_Byte(ACK);
                file_done = 1;
                session_done = 1;
                break;
              }
            }
            /* Data packet */
            // 帧头	   包号	包号反码	有效数据	校验高位	校验低位
            // SOH/STX	 PN	   XPN	    DATA	   CRC-H	  CRC-L
            else if (packets_received == 1) // 第一帧数据包,包含OTA header关键信息
            {
              memcpy(buf_ptr, packet_data + PACKET_HEADER, packet_length);
              memcpy(&ota_file_header, packet_data + PACKET_HEADER, sizeof(ota_file_header_t));

              debug_print("head:%c%c%c%c\r\n", ota_file_header.head[0], ota_file_header.head[1], ota_file_header.head[2], ota_file_header.head[3]);
              debug_print("image size:%d\r\n", ota_file_header.size);
              debug_print("img name:%s\r\n", ota_file_header.img_name);
              debug_print("img version:%d.%d.%d\r\n", ota_file_header.img_version[2], ota_file_header.img_version[1], ota_file_header.img_version[0]);
              debug_print("load address:%x\r\n", ota_file_header.img_load_address);
              debug_print("sign:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\r\n",
                          ota_file_header.sign[0],
                          ota_file_header.sign[1],
                          ota_file_header.sign[2],
                          ota_file_header.sign[3],
                          ota_file_header.sign[4],
                          ota_file_header.sign[5],
                          ota_file_header.sign[6],
                          ota_file_header.sign[7],
                          ota_file_header.sign[8],
                          ota_file_header.sign[9],
                          ota_file_header.sign[10],
                          ota_file_header.sign[11],
                          ota_file_header.sign[12],
                          ota_file_header.sign[13],
                          ota_file_header.sign[14],
                          ota_file_header.sign[15]);

              /* write user application area,packet_length-sizeof(ota_file_header_t) */
              /* Unlock page */
              debug_print("-I- Unlocking page: 0x%08x\r\n", ota_file_header.img_load_address);
              ul_rc = flash_unlock(ota_file_header.img_load_address, ota_file_header.img_load_address + ota_file_header.size, 0, 0);
              if (ul_rc != FLASH_RC_OK)
              {
                debug_print("-F- Unlock error %lu\n\r", ul_rc);
                return 0;
              }

              /* Write page */
              debug_print("-I- Writing page\n\r");
              ul_rc = flash_write(ota_file_header.img_load_address, packet_data + PACKET_HEADER + sizeof(ota_file_header_t), packet_length - sizeof(ota_file_header_t), 1);
              //ul_rc = flash_write(ota_file_header.img_load_address, packet_data + PACKET_HEADER , packet_length, 1);
              if (ul_rc != FLASH_RC_OK)
              {
                debug_print("-F- Flash programming error %lu\n\r", ul_rc);
                return 0;
              }
              else
              {
                flashdestination = ota_file_header.img_load_address + packet_length - sizeof(ota_file_header_t);
              }

              Send_Byte(ACK);
            }
            else
            {
              memcpy(buf_ptr, packet_data + PACKET_HEADER, packet_length);
              ramsource = (uint32_t)buf;

              /* Write page */
              debug_print("-I- Writing page\n\r");

              ul_rc = flash_write(flashdestination, packet_data + PACKET_HEADER, packet_length, 1);

              /* Write received data in Flash */
              if (ul_rc == FLASH_RC_OK)
              {
                flashdestination += packet_length;
                Send_Byte(ACK);
              }
              else /* An error occurred while writing to Flash memory */
              {
                debug_print("-F- Flash programming error:%d\n\r", ul_rc);
                /* End session */
                Send_Byte(CA);
                Send_Byte(CA);
                return -2;
              }
            }
            debug_print("data packet,%d\r\n", packets_received);
            packets_received++;
            session_begin = 1;
          }
        }
        break;
      case 1:
        Send_Byte(CA);
        Send_Byte(CA);
        return -3;
      default:
        if (session_begin > 0)
        {
          errors++;
        }
        if (errors > MAX_ERRORS)
        {
          Send_Byte(CA);
          Send_Byte(CA);
          return 0;
        }
        Send_Byte(CRC16);
        timeout++;
        if(timeout >= 15)
        {
          timeout = 0;
          //debug_print("timeout\r\n");
          return -5;
        }
        break;
      }
      if (file_done != 0)
      {
        break;
      }
    }
    if (session_done != 0)
    {
      break;
    }
  }

  debug_print("download finished.\r\n");

  return (int32_t)size;
}

/**
 * @brief  check response using the ymodem protocol
 * @param  buf: Address of the first byte
 * @retval The size of the file
 */
int32_t Ymodem_CheckResponse(uint8_t c)
{
  return 0;
}

/**
 * @brief  Prepare the first block
 * @param  timeout
 *     0: end of transmission
 * @retval None
 */
void Ymodem_PrepareIntialPacket(uint8_t *data, const uint8_t *fileName, uint32_t *length)
{
  uint16_t i, j;
  uint8_t file_ptr[10];

  /* Make first three packet */
  data[0] = SOH;
  data[1] = 0x00;
  data[2] = 0xff;

  /* Filename packet has valid data */
  for (i = 0; (fileName[i] != '\0') && (i < FILE_NAME_LENGTH); i++)
  {
    data[i + PACKET_HEADER] = fileName[i];
  }

  data[i + PACKET_HEADER] = 0x00;

  Int2Str(file_ptr, *length);
  for (j = 0, i = i + PACKET_HEADER + 1; file_ptr[j] != '\0';)
  {
    data[i++] = file_ptr[j++];
  }

  for (j = i; j < PACKET_SIZE + PACKET_HEADER; j++)
  {
    data[j] = 0;
  }
}

/**
 * @brief  Prepare the data packet
 * @param  timeout
 *     0: end of transmission
 * @retval None
 */
void Ymodem_PreparePacket(uint8_t *SourceBuf, uint8_t *data, uint8_t pktNo, uint32_t sizeBlk)
{
  uint16_t i, size, packetSize;
  uint8_t *file_ptr;

  /* Make first three packet */
  packetSize = sizeBlk >= PACKET_1K_SIZE ? PACKET_1K_SIZE : PACKET_SIZE;
  size = sizeBlk < packetSize ? sizeBlk : packetSize;
  if (packetSize == PACKET_1K_SIZE)
  {
    data[0] = STX;
  }
  else
  {
    data[0] = SOH;
  }
  data[1] = pktNo;
  data[2] = (~pktNo);
  file_ptr = SourceBuf;

  /* Filename packet has valid data */
  for (i = PACKET_HEADER; i < size + PACKET_HEADER; i++)
  {
    data[i] = *file_ptr++;
  }
  if (size <= packetSize)
  {
    for (i = size + PACKET_HEADER; i < packetSize + PACKET_HEADER; i++)
    {
      data[i] = 0x1A; /* EOF (0x1A) or 0x00 */
    }
  }
}

/**
 * @brief  Update CRC16 for input byte
 * @param  CRC input value
 * @param  input byte
 * @retval None
 */
uint16_t UpdateCRC16(uint16_t crcIn, uint8_t byte)
{
  uint32_t crc = crcIn;
  uint32_t in = byte | 0x100;

  do
  {
    crc <<= 1;
    in <<= 1;
    if (in & 0x100)
      ++crc;
    if (crc & 0x10000)
      crc ^= 0x1021;
  }

  while (!(in & 0x10000));

  return crc & 0xffffu;
}

/**
 * @brief  Cal CRC16 for YModem Packet
 * @param  data
 * @param  length
 * @retval None
 */
uint16_t Cal_CRC16(const uint8_t *data, uint32_t size)
{
  uint32_t crc = 0;
  const uint8_t *dataEnd = data + size;

  while (data < dataEnd)
    crc = UpdateCRC16(crc, *data++);

  crc = UpdateCRC16(crc, 0);
  crc = UpdateCRC16(crc, 0);

  return crc & 0xffffu;
}

/**
 * @brief  Cal Check sum for YModem Packet
 * @param  data
 * @param  length
 * @retval None
 */
uint8_t CalChecksum(const uint8_t *data, uint32_t size)
{
  uint32_t sum = 0;
  const uint8_t *dataEnd = data + size;

  while (data < dataEnd)
    sum += *data++;

  return (sum & 0xffu);
}

/**
 * @brief  Transmit a data packet using the ymodem protocol
 * @param  data
 * @param  length
 * @retval None
 */
void Ymodem_SendPacket(uint8_t *data, uint16_t length)
{
  uint16_t i;
  i = 0;
  while (i < length)
  {
    Send_Byte(data[i]);
    i++;
  }
}

/**
 * @brief  Transmit a file using the ymodem protocol
 * @param  buf: Address of the first byte
 * @retval The size of the file
 */
uint8_t Ymodem_Transmit(uint8_t *buf, const uint8_t *sendFileName, uint32_t sizeFile)
{

  uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD];
  uint8_t filename[FILE_NAME_LENGTH];
  uint8_t *buf_ptr, tempCheckSum;
  uint16_t tempCRC;
  uint16_t blkNumber;
  uint8_t receivedC[2], CRC16_F = 0, i;
  uint32_t errors, ackReceived, size = 0, pktSize;

  errors = 0;
  ackReceived = 0;
  for (i = 0; i < (FILE_NAME_LENGTH - 1); i++)
  {
    filename[i] = sendFileName[i];
  }
  CRC16_F = 1;

  /* Prepare first block */
  Ymodem_PrepareIntialPacket(&packet_data[0], filename, &sizeFile);

  do
  {
    /* Send Packet */
    Ymodem_SendPacket(packet_data, PACKET_SIZE + PACKET_HEADER);

    /* Send CRC or Check Sum based on CRC16_F */
    if (CRC16_F)
    {
      tempCRC = Cal_CRC16(&packet_data[3], PACKET_SIZE);
      Send_Byte(tempCRC >> 8);
      Send_Byte(tempCRC & 0xFF);
    }
    else
    {
      tempCheckSum = CalChecksum(&packet_data[3], PACKET_SIZE);
      Send_Byte(tempCheckSum);
    }

    /* Wait for Ack and 'C' */
    if (Receive_Byte(&receivedC[0], 10000) == 0)
    {
      if (receivedC[0] == ACK)
      {
        /* Packet transferred correctly */
        ackReceived = 1;
      }
    }
    else
    {
      errors++;
    }
  } while (!ackReceived && (errors < 0x0A));

  if (errors >= 0x0A)
  {
    return errors;
  }
  buf_ptr = buf;
  size = sizeFile;
  blkNumber = 0x01;
  /* Here 1024 bytes package is used to send the packets */

  /* Resend packet if NAK  for a count of 10 else end of communication */
  while (size)
  {
    /* Prepare next packet */
    Ymodem_PreparePacket(buf_ptr, &packet_data[0], blkNumber, size);
    ackReceived = 0;
    receivedC[0] = 0;
    errors = 0;
    do
    {
      /* Send next packet */
      if (size >= PACKET_1K_SIZE)
      {
        pktSize = PACKET_1K_SIZE;
      }
      else
      {
        pktSize = PACKET_SIZE;
      }
      Ymodem_SendPacket(packet_data, pktSize + PACKET_HEADER);
      /* Send CRC or Check Sum based on CRC16_F */
      /* Send CRC or Check Sum based on CRC16_F */
      if (CRC16_F)
      {
        tempCRC = Cal_CRC16(&packet_data[3], pktSize);
        Send_Byte(tempCRC >> 8);
        Send_Byte(tempCRC & 0xFF);
      }
      else
      {
        tempCheckSum = CalChecksum(&packet_data[3], pktSize);
        Send_Byte(tempCheckSum);
      }

      /* Wait for Ack */
      if ((Receive_Byte(&receivedC[0], 100000) == 0) && (receivedC[0] == ACK))
      {
        ackReceived = 1;
        if (size > pktSize)
        {
          buf_ptr += pktSize;
          size -= pktSize;
          if (blkNumber == (USER_FLASH_SIZE / 1024))
          {
            return 0xFF; /*  error */
          }
          else
          {
            blkNumber++;
          }
        }
        else
        {
          buf_ptr += pktSize;
          size = 0;
        }
      }
      else
      {
        errors++;
      }
    } while (!ackReceived && (errors < 0x0A));
    /* Resend packet if NAK  for a count of 10 else end of communication */

    if (errors >= 0x0A)
    {
      return errors;
    }
  }
  ackReceived = 0;
  receivedC[0] = 0x00;
  errors = 0;
  do
  {
    Send_Byte(EOT);
    /* Send (EOT); */
    /* Wait for Ack */
    if ((Receive_Byte(&receivedC[0], 10000) == 0) && receivedC[0] == ACK)
    {
      ackReceived = 1;
    }
    else
    {
      errors++;
    }
  } while (!ackReceived && (errors < 0x0A));

  if (errors >= 0x0A)
  {
    return errors;
  }

  /* Last packet preparation */
  ackReceived = 0;
  receivedC[0] = 0x00;
  errors = 0;

  packet_data[0] = SOH;
  packet_data[1] = 0;
  packet_data[2] = 0xFF;

  for (i = PACKET_HEADER; i < (PACKET_SIZE + PACKET_HEADER); i++)
  {
    packet_data[i] = 0x00;
  }

  do
  {
    /* Send Packet */
    Ymodem_SendPacket(packet_data, PACKET_SIZE + PACKET_HEADER);

    /* Send CRC or Check Sum based on CRC16_F */
    tempCRC = Cal_CRC16(&packet_data[3], PACKET_SIZE);
    Send_Byte(tempCRC >> 8);
    Send_Byte(tempCRC & 0xFF);

    /* Wait for Ack and 'C' */
    if (Receive_Byte(&receivedC[0], 10000) == 0)
    {
      if (receivedC[0] == ACK)
      {
        /* Packet transferred correctly */
        ackReceived = 1;
      }
    }
    else
    {
      errors++;
    }
  } while (!ackReceived && (errors < 0x0A));

  /* Resend packet if NAK  for a count of 10  else end of communication */
  if (errors >= 0x0A)
  {
    return errors;
  }

  do
  {
    Send_Byte(EOT);
    /* Send (EOT); */
    /* Wait for Ack */
    if ((Receive_Byte(&receivedC[0], 10000) == 0) && receivedC[0] == ACK)
    {
      ackReceived = 1;
    }
    else
    {
      errors++;
    }
  } while (!ackReceived && (errors < 0x0A));

  if (errors >= 0x0A)
  {
    return errors;
  }
  return 0; /* file transmitted successfully */
}

/**
 * @}
 */

/*******************(C)COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/
